package cloudspace.vm.io.console;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;
import org.zkoss.zhtml.Br;
import org.zkoss.zhtml.Div;
import org.zkoss.zk.ui.Executions;
import org.zkoss.zk.ui.HtmlMacroComponent;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zk.ui.util.Clients;
import org.zkoss.zul.Label;

import cloudspace.vm.VM;


public class ConsoleLines extends Div
{
    public static final String COMET_THREAD = "_cometThread";

    private static final String HISTORY_VISIBLE = "historyVisible";

    private static final String LOG_VISIBLE = "logVisible";

    private static final String ERR_VISIBLE = "errVisible";

    private static final String OUT_VISIBLE = "outVisible";

    private static final String STANDARD_LOG_COLOR = "logColor";

    private static final String STANDARD_ERR_COLOR = "standardErrColor";

    private static final String STANDARD_OUT_COLOR = "standardOutColor";

    private static final String TIME_STAMP = "timeStamp";

    private static final String AUTO_SCROLL = "autoScroll";

    /**
	 * 
	 */
    private static final long serialVersionUID = -2573573090331964982L;

    private boolean autoScroll = true;

    // private static final int MAX_CACHE_SIZE = 500;
    private Console vmConsole;

    private Map<String, StreamFormatter> streamFormatters;

    private boolean timeStampVis = false;

    private List<StreamLine> lineChildren;

    private GlobalStreamFilter globalFilter;

    protected Logger logger = Logger.getLogger( ConsoleLines.class );


    private class ConsoleLinesListener extends CometEvent
    {
        ConsoleLines componentLines;

        Console console;


        public ConsoleLinesListener( ConsoleLines userParent, Console consoleVM )
        {
            componentLines = userParent;
            console = consoleVM;
        }


        public void run()
        {
            try
            {
                prepare();
                if ( console.isDirty() )
                {
                    Executions.activate( desktopContext );
                    try
                    {
                        componentLines.notifyStreamUpdate();
                    }
                    finally
                    {
                        Executions.deactivate( desktopContext );
                    }
                }
                cleanup();
            }
            catch ( InterruptedException e )
            {
                logger.trace( "The console thread for desktop "
                    + desktopContext.getId()
                    + " is waiting to use the desktop." );
            }

        }

    }


    private class GlobalStreamFilter
    {
        private boolean crossDesktopVis;


        public GlobalStreamFilter()
        {
            crossDesktopVis = false;
        }


        public boolean filterLine( InfoStreamLine isl )
        {
            String desktopID = Executions.getCurrent().getDesktop().getId();
            String taggedDesktopID = isl.getDesktopID();
            if ( crossDesktopVis || taggedDesktopID == null
                || taggedDesktopID.equals( desktopID ) )
            {
                return true;
            }

            return false;
        }


        public void setDistributedVisibility( boolean flag )
        {
            crossDesktopVis = flag;

        }
    }


    private class StreamLine extends Label
    {
        /**
		 * 
		 */
        private static final long serialVersionUID = 1L;

        private InfoStreamLine infoS;

        private Br lineBr;


        public StreamLine( InfoStreamLine isl, String text, String formatColor )
        {
            super( text );
            setStyle( "color: " + formatColor );
            infoS = isl;
            lineBr = new Br();
        }


        @Override
        public boolean setVisible( boolean viz )
        {
            super.setVisible( viz );
            lineBr.setVisible( viz );
            return super.isVisible();

        }


        public String getStreamID()
        {
            return infoS.getStreamID();
        }


        public InfoStreamLine getInfoStreamLine()
        {
            return infoS;
        }


        public Br getBr()
        {
            return lineBr;
        }
    }


    private class StreamFormatter
    {
        String formatColor;

        boolean visible;


        public StreamFormatter( String color )
        {
            formatColor = color;
            visible = true;
        }


        public StreamLine createStreamComponent( InfoStreamLine isl )
        {
            String text = getFormatText( isl );
            StreamLine tempComponent = new StreamLine( isl, text, formatColor );
            tempComponent.setVisible( false );
            if ( visible )
            {
                if ( globalFilter.filterLine( isl ) )
                {
                    tempComponent.setVisible( visible );
                }
            }
            return tempComponent;
        }


        public void updateStreamComponent( StreamLine sLine )
        {
            String text = getFormatText( sLine.getInfoStreamLine() );
            sLine.setValue( text );
            sLine.setVisible( false );
            if ( visible )
            {
                if ( globalFilter.filterLine( sLine.getInfoStreamLine() ) )
                {
                    sLine.setVisible( visible );
                }
            }
        }


        private String getFormatText( InfoStreamLine isl )
        {
            String text;
            if ( timeStampVis )
            {
                text = isl.getLineWithTimestamp();
            }
            else
            {
                text = isl.getLine();
            }
            return text;
        }


        public void setVisibility( boolean flip )
        {
            visible = flip;
        }
    }


    public void onCreate()
    {
        init();
    }


    public void onExit()
    {
        Executions.getCurrent().getDesktop().enableServerPush( false );
    }


    public void init()
    {
        // autoScroll = true;
        String scrollValue = getMacroDynamicProperty( AUTO_SCROLL );
        if ( "false".equals( scrollValue ) )
        {
            autoScroll = false;
        }
        String timeStampValue = getMacroDynamicProperty( TIME_STAMP );
        if ( "false".equals( timeStampValue ) )
        {
            timeStampVis = false;
        }
        String outStreamColor = getMacroDynamicProperty( STANDARD_OUT_COLOR );
        if ( outStreamColor == null )
        {
            outStreamColor = "black";
        }
        String errStreamColor = getMacroDynamicProperty( STANDARD_ERR_COLOR );
        if ( errStreamColor == null )
        {
            errStreamColor = "red";
        }
        String logStreamColor = getMacroDynamicProperty( STANDARD_LOG_COLOR );
        if ( logStreamColor == null )
        {
            logStreamColor = "green";
        }
        boolean outVisible = true;
        boolean errVisible = true;
        boolean logVisible = false;
        boolean histVisible = false;
        String outVisibleValue = getMacroDynamicProperty( OUT_VISIBLE );
        if ( "false".equals( outVisibleValue ) )
        {
            outVisible = false;
        }
        String errVisibleValue = getMacroDynamicProperty( ERR_VISIBLE );
        if ( "false".equals( errVisibleValue ) )
        {
            errVisible = false;
        }
        String logVisibleValue = getMacroDynamicProperty( LOG_VISIBLE );
        if ( "true".equals( logVisibleValue ) )
        {
            logVisible = true;
        }
        String distributedViewValue = getMacroDynamicProperty( HISTORY_VISIBLE );
        if ( "true".equals( distributedViewValue ) )
        {
            histVisible = true;
        }
        // timeStampVis = false;
        lineChildren = new LinkedList<StreamLine>();
        globalFilter = new GlobalStreamFilter();
        globalFilter.setDistributedVisibility( histVisible );
        streamFormatters = new HashMap<String, StreamFormatter>();
        vmConsole = new Console( VM.currentVM().getStdOut(), VM.currentVM()
            .getError() );
        streamFormatters.put( vmConsole.getSystemOutStream().getStreamID(),
            new StreamFormatter( outStreamColor ) );
        streamFormatters.put( vmConsole.getSystemErrorStream().getStreamID(),
            new StreamFormatter( errStreamColor ) );
        streamFormatters.put( vmConsole.getSystemLogStream().getStreamID(),
            new StreamFormatter( logStreamColor ) );
        streamFormatters.get( vmConsole.getSystemLogStream().getStreamID() )
            .setVisibility( logVisible );
        streamFormatters.get( vmConsole.getSystemErrorStream().getStreamID() )
            .setVisibility( errVisible );
        streamFormatters.get( vmConsole.getSystemOutStream().getStreamID() )
            .setVisibility( outVisible );

        Executions.getCurrent().getDesktop().enableServerPush( true );
        CometEvent listener = new ConsoleLinesListener( this, vmConsole );
        Executions.getCurrent()
            .getDesktop()
            .setAttribute( COMET_THREAD, listener );

        CometExecutor.addListener( listener );

        Events.postEvent( "onUpdate", getFellow( "visToolbar" ), vmConsole );
    }


    private String getMacroDynamicProperty( String propertyName )
    {
        String PropertyValue = (String)( (HtmlMacroComponent)getParent().getParent() ).getDynamicProperty( propertyName );
        return PropertyValue;
    }


    public synchronized void notifyStreamUpdate()
    {
        List<InfoStreamLine> infoLines = vmConsole.getNewOrderedLines();
        linkComponents( infoLines );
        this.invalidate();
        updateScrollBox();

    }


    private void updateScrollBox()
    {
        if ( autoScroll )
            Clients.evalJavaScript( "var objDiv = document.getElementById(\"consoleLines\");objDiv.scrollTop = objDiv.scrollHeight;" );
    }


    public void linkComponents( List<InfoStreamLine> infoLines )
    {
        // if (this.getChildren().size() / 2 + infoLines.size() >
        // MAX_CACHE_SIZE)
        // {
        // int numToRemove = this.getChildren().size() / 2 + infoLines.size()
        // - MAX_CACHE_SIZE;
        // for (int i = 0; i < numToRemove * 2; i++)
        // {
        // this.getChildren().remove(0);
        // }
        // }
        // lineChildren.clear();
        for ( InfoStreamLine line : infoLines )
        {
            StreamFormatter sFormatter = streamFormatters.get( line.getStreamID() );
            StreamLine component = sFormatter.createStreamComponent( line );
            this.appendChild( component );
            lineChildren.add( component );
            this.appendChild( component.getBr() );
        }
    }


    /**
     * This notifies the Vbox that visibility has been changed on its children.
     * 
     * @param streamID
     *            the stream id to change visibility on.
     */
    public void notifyVisChange( String streamID, boolean flip )
    {
        streamFormatters.get( streamID ).setVisibility( flip );
        updateChildren();
        updateScrollBox();
    }


    public boolean isVisible( String streamID )
    {
        return streamFormatters.get( streamID ).visible;
    }


    public void notifyDisVisChange( boolean flag )
    {
        globalFilter.setDistributedVisibility( flag );
        updateChildren();
        updateScrollBox();
    }


    public boolean isHistoryVisible()
    {
        return globalFilter.crossDesktopVis;
    }


    /*
     * public void notifyAutoScrollChange() { autoScroll = !autoScroll;
     * 
     * }
     */
    public void notifyTimeStampChange()
    {
        timeStampVis = !timeStampVis;
        updateChildren();

    }


    public boolean isTimeStamp()
    {
        return timeStampVis;
    }


    private void updateChildren()
    {
        for ( StreamLine sl : lineChildren )
        {
            StreamFormatter sFormatter = streamFormatters.get( sl.getStreamID() );
            sFormatter.updateStreamComponent( sl );
        }
        this.invalidate();

    }
}
